/*
 Copyright (c) 2009 by contributors:

 * James Hight (http://labs.zavoo.com/)
 * Richard R. Masters
 * Google Inc. (Brad Neuberg -- http://codinginparadise.org)

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

package org.svgweb.core;

	import flash.utils.Dictionary;
	import flash.utils.Namespace;
	import flash.xml.XML;
	import flash.xml.XMLList;
	import haxe.PosInfos;
	import haxe.Stack;
	import me.cunity.debug.Out;
    import org.svgweb.core.SVGViewer;
	import org.svgweb.SVGViewerFlash;
    import org.svgweb.SVGViewerWeb;
    import org.svgweb.nodes.SVGANode;
    import org.svgweb.nodes.SVGAnimateColorNode;
    import org.svgweb.nodes.SVGAnimateMotionNode;
    import org.svgweb.nodes.SVGAnimateNode;
    import org.svgweb.nodes.SVGAnimateTransformNode;
    import org.svgweb.nodes.SVGAudioNode;
    import org.svgweb.nodes.SVGCircleNode;
    import org.svgweb.nodes.SVGClipPathNode;
    import org.svgweb.nodes.SVGDefsNode;
    import org.svgweb.nodes.SVGDescNode;
    import org.svgweb.nodes.SVGDOMTextNode;
    import org.svgweb.nodes.SVGEllipseNode;
    import org.svgweb.nodes.SVGFilterNode;
    import org.svgweb.nodes.SVGFontFaceNode;
    import org.svgweb.nodes.SVGFontNode;
    import org.svgweb.nodes.SVGGlyphNode;
    import org.svgweb.nodes.SVGGroupNode;
    import org.svgweb.nodes.SVGImageNode;
    import org.svgweb.nodes.SVGLinearGradient;
    import org.svgweb.nodes.SVGLineNode;
    import org.svgweb.nodes.SVGMaskNode;
    import org.svgweb.nodes.SVGMetadataNode;
    import org.svgweb.nodes.SVGMissingGlyphNode;
    import org.svgweb.nodes.SVGPathNode;
    import org.svgweb.nodes.SVGPatternNode;
    import org.svgweb.nodes.SVGPolygonNode;
    import org.svgweb.nodes.SVGPolylineNode;
    import org.svgweb.nodes.SVGRadialGradient;
    import org.svgweb.nodes.SVGRectNode;
    import org.svgweb.nodes.SVGScriptNode;
    import org.svgweb.nodes.SVGSetNode;
    import org.svgweb.nodes.SVGStopNode;
    import org.svgweb.nodes.SVGSVGNode;
    import org.svgweb.nodes.SVGSymbolNode;
    import org.svgweb.nodes.SVGTextNode;
    import org.svgweb.nodes.SVGTitleNode;
    import org.svgweb.nodes.SVGTspanNode;
    import org.svgweb.nodes.SVGUnknownNode;
    import org.svgweb.nodes.SVGUseNode;
    import org.svgweb.nodes.SVGVideoNode;
    import org.svgweb.utils.SVGUnits;
    import org.svgweb.utils.SVGUnits;
    
    import flash.display.CapsStyle;
    import flash.display.DisplayObject;
    import flash.display.GraphicsPath;
    import flash.display.JointStyle;
    import flash.display.LineScaleMode;
    import flash.display.Shape;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.MouseEvent;
    import flash.geom.Matrix;
    //import flash.utils.getDefinitionByName;
   // import flash.utils.getQualifiedClassName;
   using me.cunity.tools.StringTool;
   using me.cunity.tools.XMLTools;
   using StringTools;
	

    /** Base node extended by all other SVG Nodes **/
    class SVGNode extends EventDispatcher {
        
        public var guid(getGuid, null) :String ;
        public var id(getId, null) :String ;
        public var invalidDisplay(getInvalidDisplay, null) :Bool ;
        public var isClone(getIsClone, null) :Bool ;
        @:extern public var xml(getXml, setXml) :XML ;
        
        public static var ATTRIBUTES_NOT_INHERITED:Array<Dynamic> = ['id', 'x', 'y', 'width', 'height', 'rotate', 'transform', 
                                                'gradientTransform', 'opacity', 'mask', 'clip-path', 'href', 'target',
                                                'viewBox', 'preserveAspectRatio'];
        
        public static var INVALID_ATTR_NONE:UInt        = 0;
        public static var INVALID_ATTR_OPACITY:UInt     = 1;
        public static var INVALID_ATTR_TRANSFORM:UInt   = 2;
        public static var INVALID_ATTR_VISIBILITY:UInt  = 4;
        public static var INVALID_ATTR_DISPLAY:UInt     = 8;

        /*public namespace xlink = 'http://www.w3.org/1999/xlink';
        public namespace svg = 'http://www.w3.org/2000/svg';
        public namespace aaa = 'http://www.w3.org/XML/1998/namespace';*/
       
        public var svgRoot:SVGSVGNode ;
        public var svgParent:SVGNode ;
        public var svgChildren:Array<Dynamic> ;
        public var svgClipPathMask:SVGNode ;
        public var isMask:Bool ;

        //Used for gradients
        public var xMin:Float;
        public var xMax:Float;
        public var yMin:Float;
        public var yMax:Float;

        var _parsedChildren:Bool ;
        public var _initialRenderDone:Bool ;

        var _firstX:Bool ;
        var _firstY:Bool ;

        var _xml:XML;
        var _invalidDisplay:Bool ;
        var _invalidAttribute:UInt ;
        var _id:String ;
        var _graphicsCommands:Array<Dynamic>;
        var _styles:Dynamic;

        var original:SVGNode;
        var _isClone:Bool ;
        var _clones:Array<Dynamic> ;
        
        var animations:Array<Dynamic> ;
        
        // cache the matrix to aid any zooming and panning operations
        // later
        var _lastVBMatrix:Matrix;
        
        /**
         *
         * To handle certain flash quirks, and to support certain SVG features,
         * the implementation of one SVG node is split into one to four
         * Sprites which perform the functions of transforming, clipping, and drawing.
         * Here are the specific functions performed by each sprite:
         *
         * topSprite:
         *      * top sprite, used display list handling (parent/child relationships)
         *      * handles the transform attribute
         *      * mouse, stage, frame events
         *      * handles visibility
         *      * is the parent of the clip-path mask
         *      * has mask set to 'default' mask (top level bounding box)
         *
         * clipSprite:
         *      * has mask set to 'clip-path' mask
         *
         * drawSprite:
         *      * handles all graphics access
         *      * handles x,y,rotate,opacity attributes
         *      * filters added here
         *
         * viewBoxSprite:
         *      * handles viewBox transform
         *      * parent of SVG children
         *
         */
        public var topSprite:SVGSprite;
        public var clipSprite:SVGSprite;
        public var drawSprite:SVGSprite;
        public var viewBoxSprite:SVGSprite;
		

        /**
         * Constructor
         *
         * @param xml XML object containing the SVG document. @default null
         *
         * @return void.
         */
        public function new(svgRoot:SVGSVGNode, ?xml:XML = null, ?original:SVGNode = null)
		{
            super();
			//trace(Type.getClass(this));
            svgChildren = new Array();
            svgClipPathMask = null;
            isMask = false;
            _parsedChildren = false;
            _initialRenderDone = false;
            _firstX = true;
            _firstY = true;
            _invalidDisplay = false;
            _invalidAttribute = INVALID_ATTR_NONE;
            _id = null;
            _isClone = false;
            _clones = new Array();
			//_graphicsCommands = new Array(); 
            animations = new Array();
            this.svgRoot = svgRoot;

			this.xml = xml;

            if (null != original) {
                this.original = original;
                this._isClone = true;
            }

            if (null != topSprite) {
                topSprite.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
                topSprite.addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);
            }
            //increment('SVGNode_Constructor', new Date().getTime() - t);
        }

        public function createSVGSprites():Void {

            topSprite = new SVGSprite(this);
			topSprite.name += '::' + this._xml.localName();
			//trace(topSprite.name +'._initialRenderDone:' + _initialRenderDone);
            // This handle strange gradient bugs with negative transforms
            // by separating the transform from the drawing object
            if ( getAttribute('transform') != null 
             //if ( getAttribute('transform') != null
                 || getStyleOrAttr('clip-path') != null ) {
                  //|| getStyleOrAttr('clip-path') != null ) {
                clipSprite = new SVGSprite(this);
				clipSprite.name = topSprite.name + '_clip';
                topSprite.addChild(clipSprite);
            }
            else {
                clipSprite = topSprite;
            }

            // If the object has a gaussian filter, flash will blur the object mask,
            // even if the mask is not drawn with a blur. This is not correct rendering.
            // So, we use a stub parent object to hold the mask, in order to isolate the
            // mask from the filter. A child is created for drawing and the
            // filter is applied to the child.
            // FIXME:Currently x and y are on the drawSprite. Try to move
            // to transform sprite.
            if ( getStyleOrAttr('clip-path') != null
                       || getAttribute('x') != null
                       || getAttribute('y') != null ) {
                drawSprite = new SVGSprite(this);
				drawSprite.name = topSprite.name + '_draw';
                clipSprite.addChild(drawSprite);
            }
            else {
                drawSprite = clipSprite;
            }

            // If the object has a viewBox, the resulting transform should only apply to the
            // children of the object, so create a child sprite to hold the transform.
            // If the object is a text node, we put the TextField on the drawSprite instead of
            // the viewBoxSprite because it is simpler all children of viewBoxSprite are SVGSprites.
            //if ( getAttribute('viewBox') != null
            if ( getAttribute('viewBox') != null
                    || (Std.is( this, SVGSVGNode))  || (Std.is( this, SVGTextNode)) ) {
                viewBoxSprite = new SVGSprite(this);
				viewBoxSprite.name = topSprite.name + '_view';
                drawSprite.addChild(viewBoxSprite);
            }
            else {
                viewBoxSprite = drawSprite;
            }
        }


        /**
         * Parse the SVG XML.
         * This handles creation of child nodes.
         **/
        function parseChildren():Void {
            var text:String = '';
            for (i in 0...this._xml.children().length()) {
				var childXML:XML = _xml.children()[i];
				//trace(' childXML:' + childXML.localName());
                if (childXML.nodeKind() == 'element') {
                    // If we support text values then set them
                    if (this.hasText()) {
                        if (childXML.localName() == '__text'
                            && childXML.children().length() > 0) {
                            // for the SVGViewerWeb we use a nested
                            // SVGDOMTextNode to store the actual value; this
                            // class is necessary so that we can do text
                            // node detection in the browser and have a unique
                            // GUID per DOM text node
                            text += childXML.children().toString();
                        }
                    }
                    var newChildNode:SVGNode = this.parseNode(childXML);
                    if (null == newChildNode) {
                        continue;
                    }
                    this.addSVGChild(newChildNode);
                } else if (childXML.nodeKind() == 'text'
                           && this.hasText()) {
                    text = this._xml.text().toString();
                }  
            }
            
            if (this.hasText()) {
                this.setText(text);
            }
        }

        public function parseNode(childXML:XML):SVGNode {
            var childNode:SVGNode = null;
            var nodeName:String = childXML.localName();                    
            nodeName = nodeName.toLowerCase();
            
			switch(nodeName) {
                case "a":
                    childNode = new SVGANode(this.svgRoot, childXML);
                    
                case "animate":
                    childNode = new SVGAnimateNode(this.svgRoot, childXML);
                        
                case "animatemotion":
                    childNode = new SVGAnimateMotionNode(this.svgRoot, childXML);
                        
                case "animatecolor":
                    childNode = new SVGAnimateColorNode(this.svgRoot, childXML);
                        
                case "animatetransform":
                    childNode = new SVGAnimateTransformNode(this.svgRoot, childXML);
                        
                case "audio":
                    childNode = new SVGAudioNode(this.svgRoot, childXML);
                    
                case "circle":
                    childNode = new SVGCircleNode(this.svgRoot, childXML);
                            
                case "clippath":
                    childNode = new SVGClipPathNode(this.svgRoot, childXML);
                    
                case "desc":
                    childNode = new SVGDescNode(this.svgRoot, childXML);
                    
                case "defs":
                    childNode = new SVGDefsNode(this.svgRoot, childXML);
                    
                case "ellipse":
                    childNode = new SVGEllipseNode(this.svgRoot, childXML);
                    
                case "filter":
                    childNode = new SVGFilterNode(this.svgRoot, childXML);
                    
                case "font":
                    childNode = new SVGFontNode(this.svgRoot, childXML);
                    
                case "font-face":
                    childNode = new SVGFontFaceNode(this.svgRoot, childXML);
                    
                case "g":                       
                    childNode = new SVGGroupNode(this.svgRoot, childXML);
                    
                case "glyph":                       
                    childNode = new SVGGlyphNode(this.svgRoot, childXML);
                    
                case "image":                       
                    childNode = new SVGImageNode(this.svgRoot, childXML);
                    
                case "line":
                    childNode = new SVGLineNode(this.svgRoot, childXML);
                        
                case "lineargradient":
                    childNode = new SVGLinearGradient(this.svgRoot, childXML);
                        
                case "mask":
                    childNode = new SVGMaskNode(this.svgRoot, childXML);
                                            
                case "metadata":
                    childNode = new SVGMetadataNode(this.svgRoot, childXML);
                    
                case "missing-glyph":                       
                    childNode = new SVGMissingGlyphNode(this.svgRoot, childXML);
                    
                case "pattern":
                    childNode = new SVGPatternNode(this.svgRoot, childXML);
                    
                case "polygon":
                    childNode = new SVGPolygonNode(this.svgRoot, childXML);
                    
                case "polyline":
                    childNode = new SVGPolylineNode(this.svgRoot, childXML);
                    
                case "path":                       
                    childNode = new SVGPathNode(this.svgRoot, childXML);
                    
                case "radialgradient":
                    childNode = new SVGRadialGradient(this.svgRoot, childXML);
                        
                case "rect":
                    childNode = new SVGRectNode(this.svgRoot, childXML);
                    
                case "script":
                    childNode = new SVGScriptNode(this.svgRoot, childXML);
                    
                case "set":
                    childNode = new SVGSetNode(this.svgRoot, childXML);
                    
                case "stop":
                    childNode = new SVGStopNode(this.svgRoot, childXML);            
                    
                case "svg":
                    childNode = new SVGSVGNode(this.svgRoot, childXML, null, this.svgRoot.viewer);
                                            
                case "symbol":
                    childNode = new SVGSymbolNode(this.svgRoot, childXML);
                                            
                case "text":   
                    childNode = new SVGTextNode(this.svgRoot, childXML);
                     
                case "title":   
                    childNode = new SVGTitleNode(this.svgRoot, childXML);
                     
                case "tspan":                       
                    childNode = new SVGTspanNode(this.svgRoot, childXML);
                     
                case "use":
                    childNode = new SVGUseNode(this.svgRoot, childXML);
                    
                case "video":
                    childNode = new SVGVideoNode(this.svgRoot, childXML);
                    
                case "__text":
                    /** These are fake text nodes necessary for integration
                        with the browser through JavaScript. */
                    childNode = new SVGDOMTextNode(this.svgRoot, childXML);
                    
                case "null", "fegaussianblur":
                    
                    
                default:
                    //trace("Unknown Element:" + nodeName);
                    childNode = new SVGUnknownNode(this.svgRoot, childXML);
                        
            }

            return childNode;
        }
        
        /**
         * Called when a node is created after page load with XML content
         * added as a child. Forces a parse.
         */
        public function forceParse():Void {
            if (this._xml != null && !this._parsedChildren) {
                this.parseChildren();
                for (i in 0...this.svgChildren.length) {
                    var child:SVGNode = this.svgChildren[i];
                    child.forceParse();
                }
                
                this._parsedChildren = true;
            } 
        }

        /**
         * Triggers on ENTER_FRAME event
         * Redraws node graphics if _invalidDisplay == true
         **/
        function drawNode(?event:Event = null):Void {
            //trace((this.topSprite.parent != null )+ '&&' + this._invalidDisplay);
			if ( this.topSprite.parent != null && this._invalidDisplay) {
                     
                // are we in the middle of a suspendRedraw operation?
                if (null != this.svgRoot.viewer && this.svgRoot.viewer.isSuspended) {
                    return;
                }
                //var t:int;
                //var pieceTime:int;
                this._invalidDisplay = false;
                if (this._xml != null) {
                    //t = new Date().getTime();
                    //pieceTime = new Date().getTime();
                    drawSprite.graphics.clear();
                    //increment('drawNode_graphics.clear', new Date().getTime() - pieceTime);

                    //pieceTime = new Date().getTime();
                    if (!this._parsedChildren) {
                        this.parseChildren();
                        this._parsedChildren = true;
                    }
                    //increment('drawNode_parseChildren', new Date().getTime() - pieceTime);

                    // sets x, y, rotate, and opacity
                    //pieceTime = new Date().getTime();
                    this.setAttributes();
                    //increment('drawNode_setAttributes', new Date().getTime() - pieceTime);

                    //pieceTime = new Date().getTime();
                    if (this.getStyleOrAttr('visibility') == 'hidden') {
                        // SVG spec says visibility='hidden' should fully draw
                        // the shape with full stroke widths, etc., 
                        // just make it invisible. It also states that
                        // all children should be invisible _unless_ they
                        // explicitly have a visibility set to 'visible'.
                        this.setVisibility('hidden');
                    } else {
                        //increment('drawNode_getStyleOrAttr(visibility)', new Date().getTime() - pieceTime);
                        //pieceTime = new Date().getTime();
                        this.setVisibility('visible');
                        //increment('drawNode_setVisibility', new Date().getTime() - pieceTime);
                    }
                    
                    //pieceTime = new Date().getTime();
                    if (this.getStyleOrAttr('display', null, false) == 'none') {
                        this.topSprite.visible = false;
                    }
                    else {
                        //increment('drawNode_getStyleOrAttr(display)', new Date().getTime() - pieceTime);
                        this.topSprite.visible = true;
                    }
                    // Continue to draw even if display="none".  Animation of
                    // display attribute independent of drawNode() relies on the
                    // correct drawing state

                    //pieceTime = new Date().getTime();
                    // <svg> nodes get an implicit mask of their height and width
                    if (Std.is( this, SVGSVGNode)) {
                        this.applyDefaultMask();
                    }
                    //increment('drawNode_applyDefaultMask', new Date().getTime() - pieceTime);

                    //pieceTime = new Date().getTime();
                    this.generateGraphicsCommands();
                    //increment('drawNode_generateGraphicsCommands', new Date().getTime() - pieceTime);
                    //pieceTime = new Date().getTime();
                    this.transformNode();
                    //increment('drawNode_transformNode', new Date().getTime() - pieceTime);

                    this.draw();
                    //increment('drawNode_draw', new Date().getTime() - pieceTime);

                    //pieceTime = new Date().getTime();
                    this.applyClipPathMask();
                    //increment('drawNode_applyClipPathMask', new Date().getTime() - pieceTime);
                    //pieceTime = new Date().getTime();
                    this.applyViewBox();
                    //increment('drawNode_applyViewBox', new Date().getTime() - pieceTime);
                    //pieceTime = new Date().getTime();
                    this.setupFilters();
                    //increment('drawNode_setupFilters', new Date().getTime() - pieceTime);
                }
                
                //pieceTime = new Date().getTime();
                topSprite.removeEventListener(Event.ENTER_FRAME, drawNode);
                //increment('drawNode_removeEventListener', new Date().getTime() - pieceTime);

                //pieceTime = new Date().getTime();
                if (this.xml.attribute('id').length() == 1)  {
                    //increment('drawNode_xml.attribute('id', new Date().getTime() - pieceTime);
                    //pieceTime = new Date().getTime();
                    this.svgRoot.invalidateReferers(this.xml.attribute('id').toString());
                    //increment('drawNode_invalidateReferers', new Date().getTime() - pieceTime);
                }

                //pieceTime = new Date().getTime();
                if (getPatternAncestor() != null) {
                    //increment('drawNode_getPatternAncestor', new Date().getTime() - pieceTime);
                    //pieceTime = new Date().getTime();
                    this.svgRoot.invalidateReferers(getPatternAncestor().id);
                    //increment('drawNode_invalidateReferers', new Date().getTime() - pieceTime);
                }

                //increment('drawNodeTOTAL', new Date().getTime() - t);
            }
            
            if (!this._initialRenderDone && null !=  this.topSprite.parent) {
                this.attachEventListeners();
                this._initialRenderDone = true;
                this.svgRoot.renderFinished();
            }
        }

        function setAttributes():Void {
            this.loadAttribute('x');
            this.loadAttribute('y');
            this.loadAttribute('rotate','rotation');
            this.loadAttribute('opacity', 'alpha', true);
            if (this.getStyleOrAttr('pointer-events') == 'none') {
                topSprite.mouseEnabled = false;
                topSprite.mouseChildren = false;
            }
            else {
                topSprite.mouseEnabled = true;
                topSprite.mouseChildren = true;
            }
        }

        /**
         * Load an XML attribute into the current node
         * 
         * @param name Name of the XML attribute to load
         * @param field Name of the node field to set. If null, the name attribute will be used as the field attribute.
         * @param applyStyle Boolean Optional parameter that controls whether we
         * will look in this node's style value if the given name is not in
         * the node's attribute list. Defaults to false.
         **/
        function loadAttribute(name:String, ?field:String = null,
                                         ?applyStyle:Bool = false):Void {
            if (field == null) {
                field = name;
            }
            var tmp:String = this.getStyleOrAttr(name);
            if (tmp != null) {
                switch (name) {
                    case 'x':
                        drawSprite.x = SVGUnits.parseNumPct(tmp, this.getWidth());
                        //trace(tmp + ':' + drawSprite.x);
                    case 'y':
                        drawSprite.y = SVGUnits.parseNumPct(tmp, this.getHeight());
                        
                    case 'rotate':
                        drawSprite.rotation = SVGUnits.parseNum(tmp);
                        
                    case 'opacity':
                        drawSprite.alpha = SVGUnits.parseNum(tmp);
                        
                }
            }
        } 

        /** Sets this node and all its children to the given visibility value.
            If a child has an explicit visibility setting then that is retained
            independent of what we set here.
            
            @param visible - If 'visible', sets this node to be visible. 
            The value 'hidden' will hide it.
            @param recursive - Internal variable. Should not set. */
        function setVisibility(visible:String, 
                                         ?recursive:Bool = false):Void {
            // FIXME:Turn this into an iterative rather than a recursive
            // method
            //this.dbg('setVisibility, this='+this.xml.localName()+', visible='+visible+', recursive='+recursive);
            // ignore if we have our own visibility value and we are a recursive
            // call
            if (recursive && (this.getStyleOrAttr('visibility', null, false) != null)) {
                return;
            }
            if (null != drawSprite) {
                if (visible == 'visible') {
                    drawSprite.alpha = SVGUnits.parseNum(this.getStyleOrAttr('opacity'));
                } else {
                    drawSprite.alpha = 0;
                }
            }
            
            // set on all our children
            // ??? setting alpha on the node affects all the children
            // setting alpha = 0 means all children are hidden, regardless
            // of their own alpha values
            var child:SVGNode;
            for (i in 0...this.svgChildren.length) {
                child = this.svgChildren[i];
                child.setVisibility(visible, true);
            }
        }

        // <svg> and <image> nodes get an implicit mask of their height and width
        public function applyDefaultMask():Void {
            if ( (Std.is( this, SVGImageNode) &&
                   this.getAttribute('width') != null &&
                   this.getAttribute('height') != null )
               || Std.is( this, SVGSVGNode) ) {
                if (topSprite.mask == null) {
                    var myMask:Shape = new Shape();
                    topSprite.parent.addChild(myMask);
                    topSprite.mask = myMask;
                }
               //trace (topSprite.mask + ':' + Std.is( topSprite.mask, Shape));
                if (Std.is( topSprite.mask, Shape)) {
                    cast(topSprite.mask, Shape).graphics.clear();
                    cast(topSprite.mask, Shape).graphics.beginFill(0x000000);
                    cast(topSprite.mask, Shape).transform.matrix=topSprite.transform.matrix.clone();
                    drawSprite.graphics.clear();
                    drawSprite.graphics.beginFill(0x000000, 0);
                    var canvasWidth:Float = 0;
                    var canvasHeight:Float = 0;
                    if (Std.is( this.topSprite.parent, SVGViewer) ) {
						//var subClass:Class<Dynamic> = Type.getClass(this);
                        var clipMode:String= cast(this.topSprite.parent, SVGViewer ).getClipMode();
						//var clipMode:String = Reflect.callMethod(this, Reflect.field(this, 'getClipNode'), []);
						//trace("<svg> clip mode:"+ clipMode);
                        switch (clipMode) {
                           case 'height':
                             canvasWidth = cast(this.topSprite.parent, SVGViewer).getWidth();
                             canvasHeight = this.getHeight();
                             
                           case 'neither':
                             canvasWidth = cast(this.topSprite.parent, SVGViewer).getWidth();
                             canvasHeight = cast(this.topSprite.parent, SVGViewer).getHeight();
                             
                           case 'width':
                             canvasWidth = this.getWidth();
                             canvasHeight = cast(this.topSprite.parent, SVGViewer).getHeight();
                             
                           default:
                             this.dbg("invalid <svg> clip mode:"+ clipMode);
                           case 'both':
                             canvasWidth = this.getWidth();
                             canvasHeight = this.getHeight();
                             
                        }
                    }
                    else {
                        canvasWidth = this.getWidth();
                        canvasHeight = this.getHeight();
                    }
					
                    cast(topSprite.mask, Shape).graphics.drawRect(drawSprite.x, drawSprite.y,
                                                                  canvasWidth, canvasHeight);
                    cast(topSprite.mask, Shape).graphics.endFill();
                    // Draw an invisible rect to receive mouse events.
                    drawSprite.graphics.drawRect(drawSprite.x, drawSprite.y,
                                                 canvasWidth, canvasHeight);
                    //trace(canvasWidth + ' x ' + canvasHeight);
					//trace('drawSprite:' + this.guid + ':' +drawSprite.getBounds(drawSprite));
                    drawSprite.graphics.endFill();
                }
            }
        }

        /** 
         * Called to generate AS3 graphics commands from the SVG instructions
         **/
        function generateGraphicsCommands():Void {
            this._graphicsCommands = new Array();    
			//trace('not implemented by ' + this);
        }

        /**
         * Perform transformations defined by the transform attribute 
         **/
        public function transformNode():Void {
            topSprite.transform.matrix = new Matrix();
            this.loadAttribute('x');    
            this.loadAttribute('y');

            // Tspan x,y replaces the parent text x,y instead of
            // offsetting the parent like every other node.
            // However, if the x or y is not specified, then use the parent.
            if (Std.is( this, SVGTspanNode)) {
                if (this.getAttribute('x',null) != null) {
                    drawSprite.x = drawSprite.x - this.svgParent.getAttribute('x',0);
                }
                if (this.getAttribute('y',null) != null) {
                    drawSprite.y = drawSprite.y - this.svgParent.getAttribute('y',0);
                }
            }
            
            this.loadAttribute('rotate', 'rotation');

            // Apply transform attribute 
            var trans:String = this.getAttribute('transform');
            if (null != trans) {
                topSprite.transform.matrix = this.parseTransform(trans,
                                                            topSprite.transform.matrix.clone());
				//trace(topSprite.name +':' + trans + ':' + topSprite.transform.matrix);
            }
            var animResult:Array<Dynamic> = this.getAllAnimsTransform();
            var animMatrix:Matrix = animResult[0];
            var isAdditive:Bool = animResult[1];
            if (animMatrix != null) {
                // See Issue 311:Tranforms must be applied in correct order
                // and must account for additive attribute.
                if (isAdditive) {
                  var newMatrix:Matrix = topSprite.transform.matrix.clone();
                  animMatrix.concat(newMatrix);
                }
                topSprite.transform.matrix = animMatrix;
            }
        }

        public function parseTransform(trans:String, ?baseMatrix:Matrix = null):Matrix {
            if (null == baseMatrix) {
                baseMatrix = new Matrix();
            }
            
            if (trans != null) {
                var transArray:Array<Dynamic> = trans.match(~/\S+\(.*?\)/sg);
				//trace('transArray:' + transArray.toString());
                transArray.reverse();
                for (tran in transArray) {
                    var tranArray:Array<Dynamic> = tran.split('(',2);
                    if (tranArray.length == 2)
                    {
                        var command:String = Std.string(tranArray[0]);
                        var args:String = Std.string(tranArray[1]);
                        args = args.replace(')','');
                        args = ~/\s+/sg.replace(args,","); //Replace spaces with a comma
                        args = ~/,{2,}/sg.replace(args,","); // Remove any extra commas
                        args = ~/^,/.replace(args, ''); //Remove leading comma
                        args = ~/,_S_/.replace(args, ''); //Remove trailing comma
                        var argsArray:Array<Dynamic> = args.split(',');

                        var nodeMatrix:Matrix = new Matrix();
                        switch (command) {
                            case "matrix":
                                if (argsArray.length == 6) {
                                    nodeMatrix.a = argsArray[0];
                                    nodeMatrix.b = argsArray[1];
                                    nodeMatrix.c = argsArray[2];
                                    nodeMatrix.d = argsArray[3];
                                    nodeMatrix.tx = argsArray[4];
                                    nodeMatrix.ty = argsArray[5];
                                }
                                

                            case "translate":
                                if (argsArray.length == 1) {
                                    nodeMatrix.tx = argsArray[0]; 
                                }
                                else if (argsArray.length == 2) {
                                    nodeMatrix.tx = argsArray[0]; 
                                    nodeMatrix.ty = argsArray[1]; 
                                }
                                

                            case "scale":
                                if (argsArray.length == 1) {
                                    nodeMatrix.a = argsArray[0];
                                    nodeMatrix.d = argsArray[0];
                                }
                                else if (argsArray.length == 2) {
                                    nodeMatrix.a = argsArray[0];
                                    nodeMatrix.d = argsArray[1];
                                }
                                
                                
                            case "skewX":
                                nodeMatrix.c = Math.tan(argsArray[0] * Math.PI / 180.0);
                                
                                
                            case "skewY":
                                nodeMatrix.b = Math.tan(argsArray[0] * Math.PI / 180.0);
                                
                                
                            case "rotate":
                                if (argsArray.length == 3) {
                                    nodeMatrix.translate(-argsArray[1], -argsArray[2]);
                                    nodeMatrix.rotate(Std.parseFloat(argsArray[0])* Math.PI / 180.0);
                                    nodeMatrix.translate(argsArray[1], argsArray[2]); 
                                }
                                else {
                                    nodeMatrix.rotate(Std.parseFloat(argsArray[0])* Math.PI / 180.0);
                                }
                                
                                
                            default:
                                //this.dbg('Unknown Transformation:' + command);
                        }
                        baseMatrix.concat(nodeMatrix);
                    }
                }
            }
            
            return baseMatrix;
        }

        function draw():Void {
			//trace(_graphicsCommands.length +':' + _graphicsCommands.toString());
			if (_graphicsCommands == null)
				return;
            for (command in this._graphicsCommands) {
				//trace(command[0] + ':' + command.length);
                switch(command[0]) {
                    case "PATH":
                        this.nodeBeginFill();                       
						//trace(command[1].commands.toString() +', ' + command[1].data.join('\n') );
						//trace(command[1].commands.length +', ' + command[1].data.length + ', ' + command[1].winding);
						//trace(xml.toXMLString());
						//drawSprite.graphics.lineStyle(10);
						//drawSprite.graphics.lineTo(100, 100);
                        drawSprite.graphics.drawPath(command[1].commands, command[1].data, command[1].winding);
                        this.nodeEndFill();
						//Out.dumpLayout(drawSprite);
                        
                    case "LINE":
                        this.nodeBeginFill();
                        drawSprite.graphics.moveTo(command[1], command[2]);
                        drawSprite.graphics.lineTo(command[3], command[4]);
                        this.nodeEndFill();                
                        
                    case "RECT":
                        this.nodeBeginFill();
                        if (command.length == 5) {
                            drawSprite.graphics.drawRect(command[1], command[2],command[3], command[4]);
                        }
                        else {
                            drawSprite.graphics.drawRoundRect(command[1], command[2],command[3], command[4], command[5], command[6]);
                        }
                        this.nodeEndFill();                
                                
                    case "CIRCLE":
                        this.nodeBeginFill();
                        drawSprite.graphics.drawCircle(command[1], command[2], command[3]);
                        this.nodeEndFill();
                        
                    case "ELLIPSE":
                        this.nodeBeginFill();                        
                        drawSprite.graphics.drawEllipse(command[1], command[2],command[3], command[4]);
                        this.nodeEndFill();
                        
					default:
					trace('not implemented:' + command[0]);
                }
				//Out.dumpLayout(drawSprite);
            }
        }

        /** 
         * Called at the start of drawing an SVG element.
         * Sets fill and stroke styles
         **/
        function nodeBeginFill():Void {
            //Fill
            var color_and_alpha:Array<Dynamic> = [0, 0];
            var color_core:Int = 0;
            var color_alpha:Float = 0;
            var fill_alpha:Float = 0;

            var fill:String = this.getStyleOrAttr('fill');
            // draw even if visibility = "hidden".  May be required for
            // pointer-events. Animation of visibility attribute independent of
            // drawNode() relies on the correct drawing state

			var eR:EReg = ~/url\(#([^\)]+)\)/si;
			if (eR.match(fill))
			{
				//trace(fill + ':'  + eR.matched(1));
                var fillName:String = eR.matched(1);
                this.svgRoot.addReference(this, fillName);
                var fillNode:SVGNode = this.svgRoot.getNode(fillName);
				//trace(Type.getClass(fillNode) +':' + Std.is( fillNode, SVGGradient));
                if (null == fillNode) {
                     // this happens normally
                     this.dbg("Gradient " + fillName + " not (yet?) available for " + this.xml.attribute('id'));
                }
                if (Std.is( fillNode, SVGGradient)) {
                    cast(fillNode, SVGGradient).beginGradientFill(this);
                }
                else if (Std.is( fillNode, SVGPatternNode)) {
                    cast(fillNode, SVGPatternNode).beginPatternFill(this);
                }
            }
            else {
                if (fill == 'currentColor') {
                    fill = this.getStyleOrAttr('color');
                }
                // Still may draw for fill 'none' in order to get mouse events.
                if (fill != 'none') {
                    color_and_alpha = SVGUnits.getColorAndAlpha(fill);
                    color_core = color_and_alpha[0];
                    color_alpha = color_and_alpha[1];
                    fill_alpha = SVGUnits.parseNum( this.getStyleOrAttr('fill-opacity') ) * color_alpha;
                }
                var pointerEvents:String = this.getStyleOrAttr('pointer-events', 'visiblePainted');
                // Begin fill if there is a fill set, or if we need an invisible fill to get mouse events
                if (   (fill != 'none' && getStyleOrAttr('visibility') != 'hidden') 
                    || (pointerEvents == 'all') ) {
                    drawSprite.graphics.beginFill(color_core, fill_alpha);
                }

            }
            nodeBeginStroke();
        }

        function nodeBeginStroke():Void {
            var shapeRendering:String = this.getStyle('shape-rendering'); 
            
            //Stroke
            var line_color:UInt;
            var line_alpha:Float;
            var line_width:Float;

            var stroke:String = this.getStyleOrAttr('stroke', 'black');
            if ( (stroke == 'none') || (stroke == '') || (this.getStyleOrAttr('visibility') == 'hidden') ) {
                line_alpha = 0;
                line_color = 0;
                line_width = 0;
            }
            else {
                if (stroke == 'currentColor') {
                    stroke = this.getStyleOrAttr('color');
                }
                line_color = Std.int(SVGUnits.parseNum(SVGUnits.getColor(stroke)));
                line_alpha = SVGUnits.parseNum(this.getStyleOrAttr('stroke-opacity'));
                line_width = SVGUnits.parseNum(this.getStyleOrAttr('stroke-width'));
            }

            //var capsStyle:String = this.getStyleOrAttr('stroke-linecap');
			var capsStyle:CapsStyle = switch(this.getStyleOrAttr('stroke-linecap'))
			{
				case 'round':
				CapsStyle.ROUND;
				
				case  'square':
				CapsStyle.SQUARE;
				
				default:
				CapsStyle.NONE;				
			}

            
            var jointStyle:JointStyle = switch(this.getStyleOrAttr('stroke-linejoin'))
			{
				case 'round':
                JointStyle.ROUND;
				
				case 'bevel':
                JointStyle.BEVEL;
				
				default:
				// Issue 437:"Slow rendering with mitered corners"
				// http://code.google.com/p/svgweb/issues/detail?id=437
				// The SVG default is to use mitered joins, but this is _extremely_ slow on
				// Flash for some edge cases; using the Flash default joint style
				// is much faster, by about two orders of magnitude for certain
				// SVG files.
				if (shapeRendering == 'optimizeSpeed' || shapeRendering == 'auto')
					JointStyle.ROUND;
				else
					JointStyle.MITER;
			}
                                   
            var miterLimit:String = this.getStyleOrAttr('stroke-miterlimit');
            if (miterLimit == null) {
                miterLimit = '4';
            }
            
			//trace('lineStyle(' + line_width + ', ' + line_color +', ' + line_alpha);
            drawSprite.graphics.lineStyle(line_width, line_color, line_alpha, false, LineScaleMode.NORMAL,
                                          capsStyle, jointStyle, SVGUnits.parseNum(miterLimit));
            // draw even if visibility = "hidden".  May be required for
            // pointer-events. Animation of visibility attribute independent of
            // drawNode() relies on the correct drawing state
            if ( (stroke != 'none') && (stroke != '') ) {
                var strokeMatches:Array<Dynamic> = stroke.match(~/url\(#([^\)]+)\)/si);
                if (strokeMatches != null && strokeMatches.length > 0) {
                    var strokeName:String = strokeMatches[1];
                    this.svgRoot.addReference(this, strokeName);
                    var strokeNode:SVGNode = this.svgRoot.getNode(strokeName);
                    if (null == strokeNode) {
                         // this happens normally
                         this.dbg("stroke gradient " + strokeName + " not (yet?) available for " + this.xml.attribute('id').toString());
                    }
                    if (Std.is( strokeNode, SVGGradient)) {
                         cast(strokeNode, SVGGradient).lineGradientStyle(this, Std.int(line_alpha));
                    }
                }
            }
        }
        
        /** 
         * Called at the end of drawing an SVG element
         **/
        function nodeEndFill():Void {
            drawSprite.graphics.endFill();
        }

        /**
         * Check value of x against _minX and _maxX, 
         * Update values when appropriate
         **/
        function setXMinMax(value:Float):Void {          
            if (_firstX) {
                _firstX = false;
                this.xMax = value;
                this.xMin = value;
                return;
            }
            
            if (value < this.xMin) {
                this.xMin = value;
            }
            if (value > this.xMax) {
                this.xMax = value;
            }
        }
        
        /**
         * Check value of y against _minY and _maxY, 
         * Update values when appropriate
         **/
        function setYMinMax(value:Float):Void {
            if (_firstY) {
                _firstY = false;
                this.yMax = value;
                this.yMin = value;
                return;
            }
            
            if (value < this.yMin) {
                this.yMin = value;
            }
            if (value > this.yMax) {
                this.yMax = value;
            }
        }

        public function applyViewBox():Void {
            // Apply viewbox transform
            var viewBox:String = this.getAttribute('viewBox');
			//viewBox = null;
            var preserveAspectRatio:String = this.getAttribute('preserveAspectRatio');

            if ( (viewBox != null) || (preserveAspectRatio != null) ) {
                var newMatrix:Matrix = new Matrix();

                if (preserveAspectRatio == null) {
                    preserveAspectRatio = 'xMidYMid meet';
                }

                /**
                 * Canvas, the viewport
                 **/
                var canvasWidth:Float;
                var canvasHeight:Float;
                if (Std.is( topSprite.parent, SVGViewer)) {
                    canvasWidth = cast(topSprite.parent, SVGViewer).getWidth();
                    canvasHeight = cast(topSprite.parent, SVGViewer).getHeight();
                }
                else {
                    canvasWidth = this.getWidth();
                    canvasHeight = this.getHeight();
                }
				
                /**
                 * Viewbox
                 **/
                var viewX:Float;
                var viewY:Float;
                var viewWidth:Float;
                var viewHeight:Float;
                if (viewBox != null) {
                    viewBox = ~/,/sg.replace(viewBox," "); //Replace commas with spaces
                    var points:Array<Dynamic> = ~/\s+/g.split(viewBox); //Split by white space 
                    viewX = SVGUnits.parseNum(points[0]);
                    viewY = SVGUnits.parseNum(points[1]);
                    viewWidth = SVGUnits.parseNum(points[2]);
                    viewHeight = SVGUnits.parseNum(points[3]);
                }
                else {
                    viewX = 0;
                    viewY = 0;
                    viewWidth = canvasWidth;
                    viewHeight = canvasHeight;
                }
				
				
                var oldAspectRes:Float = viewWidth / viewHeight;
                var newAspectRes:Float = canvasWidth /  canvasHeight;
                var cropWidth:Float;
                var cropHeight:Float;

                var alignMode:String = preserveAspectRatio.substr(0,8);
                var meetOrSlice:String = 'meet';
                if (preserveAspectRatio.indexOf('slice') != -1) {
                    meetOrSlice = 'slice';
                }

                /**
                 * Handle Scaling
                 **/
                if (alignMode == 'none') {
                    // stretch to fit viewport width and height

                    cropWidth = canvasWidth;
                    cropHeight = canvasHeight;
                }
                else {
                    if (meetOrSlice == 'meet') {
                        // shrink to fit inside viewport

                        if (newAspectRes > oldAspectRes) {
                            cropWidth = canvasHeight * oldAspectRes;
                            cropHeight = canvasHeight;
                        }
                        else {
                            cropWidth = canvasWidth;
                            cropHeight = canvasWidth / oldAspectRes;
                        }
    
                    }
                    else {
                        // meetOrSlice == 'slice'
                        // Expand to cover viewport.

                        if (newAspectRes > oldAspectRes) {
                            cropWidth = canvasWidth;
                            cropHeight = canvasWidth / oldAspectRes;
                        }
                        else {
                            cropWidth = canvasHeight * oldAspectRes;
                            cropHeight = canvasHeight;
                        }
    
                    }
                }
                var scaleX:Float = cropWidth / viewWidth;
                var scaleY:Float = cropHeight / viewHeight;
                newMatrix.translate(-viewX, -viewY);
                newMatrix.scale(scaleX, scaleY);
				
                /**
                 * Handle Alignment
                 **/
                var borderX:Float;
                var borderY:Float;
                var translateX:Float;
                var translateY:Float;
                if (alignMode != 'none') {
                    translateX=0;
                    translateY=0;
                    var xAlignMode:String = alignMode.substr(0, 4);
					//trace(xAlignMode + ' - ' + yAlignMode);
					//trace(xAlignMode + ' - ' + yAlignMode);
                    switch (xAlignMode) {
                        case 'xMin':
						case 'xMax':
                            translateX = canvasWidth - cropWidth;
                            
                        //case 'xMid':						
                        default:
                            borderX = canvasWidth - cropWidth;
                            translateX = borderX / 2.0;

                    }
                    var yAlignMode:String = alignMode.substr(4,4);
                    switch (yAlignMode) {
                        case 'YMin':
						case 'YMax':
                            translateY = canvasHeight - cropHeight;
                            
                        //case 'YMid':
                        default:
                            borderY = canvasHeight - cropHeight;
                            translateY = borderY / 2.0;
                            
                    }
                    newMatrix.translate(translateX, translateY);
                }
                
                // cache the matrix to efficiently aid any zooming and 
                // panning operations later
                this._lastVBMatrix = newMatrix.clone();
                
                // apply any zoom and pan values that might be in effect
                // if we are the SVG root tag
                if (Std.is( this, SVGSVGNode) && this.svgParent == null) {
                  newMatrix.translate(this.svgRoot.currentTranslate.x, 
                                      this.svgRoot.currentTranslate.y);
                  newMatrix.scale(this.svgRoot.currentScale, 
                                  this.svgRoot.currentScale);
                }
                
                viewBoxSprite.transform.matrix = newMatrix;
            }
        }

        function applyClipPathMask():Void {
            var attr:String;
            var match:Array<Dynamic>;
            var node:SVGNode;
            var matrix:Matrix;

            attr = this.getStyleOrAttr('mask');
            if (null == attr) {
                attr = this.getStyleOrAttr('clip-path');
            }
			//trace(id +':' + attr);
            if (null != attr && attr != 'none') {
               match = attr.match(~/url\(\s*#(.*?)\s*\)/si);
               if (match.length == 2) {
                   attr = match[1];
                   node = this.svgRoot.getNode(attr);
                   if (svgClipPathMask == node) {
                       return;
                   }
                   this.removeMask();
                   if (null != node) {
                       svgClipPathMask = node;

                       var newMask:SVGNode = node.clone();
                       newMask.isMask = true;

                       addSVGChildMask(newMask);
                       clipSprite.mask = newMask.topSprite;

                       newMask.topSprite.visible = true;
                       clipSprite.cacheAsBitmap = true;
                       // We need to cacheAsBitmap for group and path nodes
                       // to fix a strange problem with a path on line 336
                       // of samples/svg-files/juanmontoya_lingerie.svg
                       // Without cacheAsBitmap, this path is not clipped by the
                       // group node mask on line 329.
                       // Issue 345:cacheAsBitmap should only be used when
                       // necessary due to its large memory consumption.
                       if (Std.is( this, SVGPathNode) || Std.is( this, SVGGroupNode)) {
                           newMask.topSprite.cacheAsBitmap = true;
                       }
                   }
               }
            }
            else {
                this.removeMask();
            }
        }

        function removeMask():Void {
            if (clipSprite != topSprite && null != clipSprite.mask) {
                clipSprite.mask.parent.removeChild(clipSprite.mask);
                clipSprite.mask = null;
                svgClipPathMask = null;
            }
        }

        /**
         * Add any assigned filters to node
         **/
        function setupFilters():Void {
            var filterName:String = this.getStyleOrAttr('filter');
            if ( (filterName != null) && (filterName != '') && (filterName != 'none') ) {
                var matches:Array<Dynamic> = filterName.match(~/url\(#([^\)]+)\)/si);
				//trace(matches);
                if (matches.length > 0) {
                    filterName = matches[1];
                    var filterNode:SVGNode = this.svgRoot.getNode(filterName);
                    if (null != filterNode) {
                        drawSprite.filters = untyped cast(filterNode, SVGFilterNode).getFilters(this);
                        //drawSprite.filters.push( cast(filterNode, SVGFilterNode).getFilters(this));
                    }
                    else {
                        this.dbg("filter " + filterName + " not (yet?) available for " + this.xml.attribute('id'));
                        // xxx add reference
                    }
                }
            }
        }

        function attachEventListeners():Void {
            var action:String;

            action = this.getAttribute('onclick', null, false);
            if (null != action) {
                this.svgRoot.addActionListener(MouseEvent.CLICK, drawSprite);
            }

            action = this.getAttribute('onmousedown', null, false);
            if (null != action) {
                this.svgRoot.addActionListener(MouseEvent.MOUSE_DOWN, drawSprite);
            }

            action = this.getAttribute('onmouseup', null, false);
            if (null != action) {
                this.svgRoot.addActionListener(MouseEvent.MOUSE_UP, drawSprite);
            }

            action = this.getAttribute('onmousemove', null, false);
            if (null != action) {
                this.svgRoot.addActionListener(MouseEvent.MOUSE_MOVE, drawSprite);
            }

            action = this.getAttribute('onmouseover', null, false);
            if (null != action) {
                this.svgRoot.addActionListener(MouseEvent.MOUSE_OVER, drawSprite);
            }

            action = this.getAttribute('onmouseout', null, false);
            if (null != action) {
                this.svgRoot.addActionListener(MouseEvent.MOUSE_OUT, drawSprite);
            }
        }
                

        /*
         * Node registration triggered by stage add / remove
         */


        // FIXME:Move to child add/remove because the node may
        // not be added to the stage.
        function onAddedToStage(event:Event):Void {
            this.registerSelf();
            if (null != this.original) {
                this.original.registerClone(this);
            }
        }

        function onRemovedFromStage(event:Event):Void {
            topSprite.removeEventListener(Event.ENTER_FRAME, drawNode);
            this.unregisterSelf();
            if (null != this.original) {
                this.original.unregisterClone(this);
            }
        }

        function registerSelf():Void {
            if (this._isClone || (getMaskAncestor() != null)) {
                return;
            }
            
            this.svgRoot.registerGUID(this);
            
            var id:String = this.getAttribute('id');

            if (id == _id) {
                return;
            }

            if (_id != null) {
                this.svgRoot.unregisterNode(this);
            }

            if (id != null && id != "") {
                _id = id;
                this.svgRoot.registerNode(this);
            }
        }

        function unregisterSelf():Void {
            if (this._isClone || (getMaskAncestor() != null)) {
                return;
            }

            this.svgRoot.unregisterGUID(this);
            
            if (this._id != null) {
                this.svgRoot.unregisterNode(this);
                _id = null;
            }
        }

        /*
         * Attribute Handling
         */

        /** 
         * Gets an XML attribute from this node
         *
         * @param name XML attribute to retrieve from SVG XML.
         * @param defaultValue Default value to return if this attribute is
         * not defined. Defaults to null.
         * @param inherit Whether to look up the inheritance chain if this
         * property is inherited. Defaults to true.
         * @param applyAnimations Boolean, optional, defaults to true. Controls
         * whether we apply any SMIL animations that might be in flight to the
         * attribute. If false, then we return the base actual set value on
         * this node without reference to any animation changes.
         * @param applyStyle Boolean, optional, defaults to false. Controls whether
         * we get and set values from this node's style values as well as its
         * attribute values. If false, then we don't check or set a given
         * attribute name against the style value.
         * 
         * @return Returns the XML attribute value, or the defaultValue.
         **/
        public function getAttribute(name:String, 
                                     ?defaultValue:Dynamic = null,
                                     ?inherit:Bool = true,
                                     ?applyAnimations:Bool = true,
                                     ?applyStyle:Bool = false):Dynamic {
            var value:String = this._getAttribute(name, defaultValue,
                                                  inherit, applyAnimations,
                                                  applyStyle);
            
            if (value != null && value != 'inherit') {
                return value;
            }
            
            if (value == "inherit") {
                value = null;
            }
            
            if (ATTRIBUTES_NOT_INHERITED.indexOf(name) != -1) {            
                if (defaultValue == null) {
                    if (name == 'opacity') {
                        return '1';
                    }
                    // default fall through
                }
                return defaultValue;        
            }
            
            if (inherit && (this.svgParent != null)) {
                return cast(this.svgParent, SVGNode).getAttribute(name, defaultValue, true, 
                                                                 applyAnimations, applyStyle);
            }
            
            return defaultValue;       
        }
        
        /**
         *
         * This method retrieves the attribute from the current node only and is
         * used as a helper for getAttribute, which has css parent inheritance logic.
         *
         **/
        function _getAttribute(name:String, ?defaultValue:Dynamic = null,
                                         ?inherit:Bool = true,
                                         ?applyAnimations:Bool = true,
                                         ?applyStyle:Bool = false):Dynamic {
            var value:String;
            
            // If we are rendering a mask, then use a simple black fill.
            if (this.getMaskAncestor() != null) {
                if (  (name == 'opacity')
                    || (name == 'fill-opacity')
                    || (name == 'stroke-width')
                    || (name == 'stroke-opacity') ) {
                    return '1';
                }

                if (name == 'fill') {
                    return 'black';
                }

                if (name == 'stroke') {
                    return 'none';
                }

                if (name == 'filter') {
                    return 'none';
                }
            }

            if (applyAnimations) {
                value = getAnimAttribute(name, defaultValue, inherit,
                                         applyStyle);
                if (value != null) {
                    return value;
                }
            }
           
            if (name == "href") {
                //this._xml@href handled normally
				//trace(xml.toXMLString() + ' href:' + _xml.attribute('xlink:href').length());
                //value = this._xml.attribute('xlink:href').toString();                             
                value = _xml.getAttributeNS('href');       
				//if (value != null && (value != ""))				trace(value);
				//else 
				//trace(Stack.toString(Stack.callStack()));
				
                if (value != null && (value != "")) {
                    return value;
                }
            }

            if (name == "base") {
                //value = this._xml.attribute('aaa:base').toString();                             
                value = this._xml.getAttributeNS('base');                             
                if (value != null && (value != "")) {
                    return value;
                }
            }

            // If node is the top level of a clone,
            // check for an override value from the parent.
            if (null != this.original && (Std.is( this.svgParent, SVGUseNode))) {
                // <use> node already implements x, y, and transform
                if (name != 'x' && name != 'y' && name != 'transform') {
                    value = this.svgParent._getAttribute(name, null, false, true, true);
                    if (value != null) {
                        return value;
                    }
                }
            }
           
            if (applyStyle && _styles.hasOwnProperty(name)) {
                return Reflect.field(_styles, name);
            }
           
            var xmlList:XMLList = this._xml.attribute(name);
            
            if (xmlList.length() > 0) {
                return xmlList[0].toString();
            }
 
            return null;
        }
        
        public function getStyleOrAttr(name:String, ?defaultValue:Dynamic = null,
                                       ?inherit:Bool = true,
                                       ?applyAnimations:Bool = true):Dynamic {
            return getAttribute(name, defaultValue, inherit, applyAnimations, true);
        }
        
        // process all animations
        public function getAnimAttribute(name:String, ?defaultValue:Dynamic = null,
                                         ?inherit:Bool = true,
                                         ?useStyle:Bool = false):String {
            var animation:SVGAnimateNode;

            // transform is handled by getAllAnimsTransforms
            if (name == "transform")
                return null;
 
            var foundAnimation:Bool = false;
            for (animation in animations) {
                if (animation.getAttributeName() == name) {
                    foundAnimation = true;
                    break;
                }
            }
			//trace(animations.length + ':' + foundAnimation);
            if (!foundAnimation)
                return null;
 
            var isAdditive:Bool = true;
            // Start with base value
            var baseVal:String = getAttribute(name, defaultValue, inherit, false,
                                              useStyle);
            var isPath:Bool = name=='d';
            var animVal:Float = 0;
            if (baseVal != null) {
                animVal = SVGUnits.parseNum(baseVal);
            }
            else if (!isPath) {
                animVal= 0;
            }
             
            // Handle discrete string values
            var discreteStringVal:String = null;
            var isColor:Bool = (baseVal != null) && !isPath && SVGUnits.isColor(baseVal);
            var animValString:String = null;
            // XXX This should sort by priority (activation order) 
            // Add or replace with animations
            for (animation in animations) {
                if (   animation.getAttributeName() == name
                    && animation.isEffective() ) {
                    animValString = animation.getAnimValue();
                    if (animValString == null) continue;  // null is an error, or !isEffective
                    isColor = !isPath && (isColor || SVGUnits.isColor(animValString));
                    if (animation.isAdditive()) {
                        if (isColor) {
                            animVal = SVGUnits.addColors(animVal, SVGUnits.parseNum(animValString));
                        } else if (!isPath) {
                            animVal = animVal + SVGUnits.parseNum(animValString);
                        }
                    }
                    else {
                        if (isPath || Math.isNaN(animVal = SVGUnits.parseNum(animValString))) {
                           discreteStringVal = animValString;
                        }
                    }
                }
            }
            if (animValString == null) //no animations
                return baseVal;
			trace('animValString:' + animValString + ' discreteStringVal:' +discreteStringVal + ' animVal:' + animVal);
            if (null != discreteStringVal) {
                return discreteStringVal;
            } else {
                if (isColor) {
                    return SVGUnits.colorString(Std.int(animVal));
                } else {
                    return Std.string(animVal);
                }
            }
        }

        /** 
         * Compute cumulative animation transform.
         **/
        public function getAllAnimsTransform():Array<Dynamic> {
            var animTransform:Matrix= new Matrix();
            var isAdditive:Bool = true;

            // Issues 254, 311 and 331 are relevant to transform ordering.
            // The additive attribute is processed in order of appearance.
            var animsToApply:Array<Dynamic> = new Array();
            for (animation in animations) {
                if (Std.is( animation, SVGAnimateTransformNode) && animation.isEffective()) {
                    var transAnim:SVGAnimateTransformNode = cast(animation, SVGAnimateTransformNode);
                    if (!transAnim.isAdditive()) {
                        isAdditive=false;
                        animsToApply = new Array();
                    }
                    animsToApply.push(transAnim);
                }
            }
            // Transform animations are applied in reverse order of appearance.
			animsToApply.reverse();
            var animReverse:Array<Dynamic> = animsToApply;
            for (transAnim in animReverse) {
                animTransform.concat(transAnim.getAnimTransform());
            }

            return [ animTransform, isAdditive ];
        }

        public function setAttribute(name:String, value:String, ?attrNamespace:String = null):Void {
            trace(id + ':' + name + '->' + value);
			if (name == "style") {
               Reflect.setField( this._xml , "@style", value);
                this.parseStyle();
            }
            if (attrNamespace != null) {
                // namespaced attribute, such as xlink:href
                var ns:Namespace = new Namespace(attrNamespace);
                Reflect.setField(this._xml,  '@'+attrNamespace + ':' + name, value);
            }
            else {
				Reflect.setField(this._xml, '@'+name, value);
            }
            handleAttrChange(name, value, attrNamespace);
        }
            
        public function handleAttrChange(name:String, value:String, ?attrNamespace:String = null):Void {
            switch (name) {
                case 'onclick', 'onmousedown', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup':
                    this.attachEventListeners();
                 
                case 'transform', 'viewBox', 'x', 'y', 'rotation':
                    this.transformNode();
                    this.applyViewBox();                    

                case 'pointer-events':
                    if (value  == 'none') {
                        topSprite.mouseEnabled = false;
                        topSprite.mouseChildren = false;
                    }
                    else {
                        topSprite.mouseEnabled = true;
                        topSprite.mouseChildren = true;
                    }
                    
                default:
                    this.invalidateDisplay();
                    if (    (name == 'display' || name == 'visibility')
                         || (name == 'style' && value != null &&
                              ( value.indexOf('visibility') != -1
                              || value.indexOf('display') != -1 ))
                         || (Std.is( this, SVGGroupNode) || Std.is( this, SVGTextNode)) ) {
                        this.invalidateChildren();
                    }
                    
            }
            this.updateClones();
            if (getClipPathAncestor() != null) {
                getClipPathAncestor().updateClones();
            }
            if (getPatternAncestor() != null) {
                this.svgRoot.invalidateReferers(getPatternAncestor().id);
            }
        }
        
        /** Sets a style attribute in the style="" string. Note that this
         *  leaves XML attributes alone. For examle, if you set 
         *  the fill style to 'red', the XML fill attribute will remain with
         *  its old value. 
         **/
        public function setStyle(name:String, value:String):Void {
            //this.dbg('setStyle, name='+name+', value='+value);
            Reflect.setField(this._styles, name, value);
            this.updateStyle();
            this.parseStyle();
            
            handleAttrChange(name, value, null);
        }
        
        /** Gets a style attribute from the style="" string. Note that this
         *  leaves XML attributes alone. For examle, if you set 
         *  the fill style to 'red', the XML fill attribute will remain with
         *  its old value. Note that style values do not reflect changes
         *  that might apply due to SMIL animations; use getAttribute to
         *  an attribute that might have come into existence as part of a 
         *  SMIL animation.
         *
         *  @param name The style name, such as fill or stroke-width. Dashed
         *  style names will automatically be converted into camel case.
         *  @param defaultValue The default value to return if none is present.
         *  Defaults to null.
         *  @param inherit Whether to look up the inheritance chain if this
         *  property is inherited. Defaults to true.
         *
         *  @return The style value, or null if it is not present.
         **/
        public function getStyle(name:String, ?defaultValue:Dynamic = null, 
                                 ?inherit:Bool = true):Dynamic {
            var value:String = this._getStyle(name);
            if (value != null) {
                return value;
            }
                     
            if (_styles.hasOwnProperty(name)) {
                value = Reflect.field(_styles, name);
            }
            
            if (value == "inherit") {
                value = null;
            }
            
            if (value != null) {
                return value;
            }
            
            if (inherit && null != this.svgParent) {
                return this.svgParent.getStyle(name, defaultValue, inherit);
            }
            
            return defaultValue;
        }
        
        /** This method retrieves the style from the current node only and is
         *  used as a helper for getStyle. 
         **/
        function _getStyle(name:String):String {
            var value:String;
            
            // If we are rendering a mask, then use a simple black fill.
            if (this.getMaskAncestor() != null) {
                if ( (name == 'opacity')
                    || (name == 'fill-opacity')
                    || (name == 'stroke-width')
                    || (name == 'stroke-opacity') ) {
                    return '1';
                }

                if (name == 'fill') {
                    return 'black';
                }

                if (name == 'stroke') {
                    return 'none';
                }

                if (name == 'filter') {
                    return null;
                }
            }
            
            if (null != this.original && (Std.is( this.svgParent, SVGUseNode))) {
                //Node is the top level of a clone
                //Check for an override value from the parent
                value = this.svgParent.getStyle(name, null, false);
                if (value != null) {
                    return value;
                }
            }

            if (_styles.hasOwnProperty(name)) {
                return Reflect.field(_styles, name);
            }
 
            return null;
        }

        function parseStyle():Void {
            //Get styling from XML attribute 'style'
            _styles = {};
            
            var xmlList:XMLList = this._xml.attribute('style');
            if (xmlList.length() > 0) {
                var styleString:String = xmlList[0].toString();
                
                // only one style value given, with no trailing semicolon?
                if ( styleString.indexOf(';') == -1) {
                    // update our internal string to be correct moving forward
                    Reflect.setField(this._xml, '@style', styleString + ';');
                    styleString += ';';
                }
                
                var styles:Array<String> = styleString.split(';');
                for (style in styles) {
                    var styleSet:Array<String> = style.split(':');
                    if (styleSet.length == 2) {
                        var attrName:String = SVGUnits.trim(styleSet[0]);
                        var attrValue:String = SVGUnits.trim(styleSet[1]);                        
                        Reflect.setField(this._styles, attrName, attrValue);
                    }
                }
            }
        }
 
       /**
         * Update style attribute from _styles
         * <node style="...StyleString...">
         * 
         **/ 
        function updateStyle():Void {
            var newStyleString:String = '';
            
            for ( key in Reflect.fields(this._styles)) {
                newStyleString += key + ':' + Reflect.field(this._styles, key) + ';';
            }
            
            Reflect.setField(this._xml, 'style', newStyleString);
        }
        
        /**
         * Returns true if this node can have text children. Subclasses
         * should override this if they want to have text node children.
         */
        public function hasText():Bool {
            return false;
        }
        
        /**
         * Returns any text content this node might have as a child.
         */
        public function getText():String {
            return this._xml.text().toString();
        }
        
        /**
         * Sets any text content this node might have as a child.
         */
        public function setText(newValue:String):Void {
            // subclasses should implement this if they want to have text
            throw("Unimplemented");
        }
        
        public function onRegisterFont(fontFamily:String):Void {
        }

        /**
         * Force a redraw of a node
         **/
        public function invalidateDisplay():Void {
            if (!this._invalidDisplay  && null != topSprite) {
                this._invalidDisplay = true;
                topSprite.addEventListener(Event.ENTER_FRAME, drawNode);
                if (this._invalidAttribute != 0) {
                    // if _invalidAttribute is already set then reset to prevent
                    // repeat processing performed by drawNode.
                    this._invalidAttribute = INVALID_ATTR_NONE;
                }            
            }
        }

        public function invalidateChildren():Void {
            var child:SVGNode;
            for (i in 0...viewBoxSprite.numChildren) {
                var node:DisplayObject = viewBoxSprite.getChildAt(i);
                if (!(Std.is( node, SVGSprite))) {
                  // Issue 391:
                  // "Get Jessyink demo working on SVG Web"
                  // http://code.google.com/p/svgweb/issues/detail?id=391
                  continue;
                }
                child = cast(node, SVGSprite).svgNode;
                child.invalidateDisplay();
                child.invalidateChildren();
            }
        }
 
        public function invalidateAttribute(attributeName:String):Void {
            if (null == this.topSprite || null == this.topSprite.parent || this._invalidDisplay) return;
            var attr:UInt = INVALID_ATTR_NONE;
            switch (attributeName) {
                case 'transform', 'viewBox', 'x', 'y', 'rotation':
                    attr = INVALID_ATTR_TRANSFORM;
                    
                case 'opacity' :
                    attr = INVALID_ATTR_OPACITY;
                    
                case 'visibility' :
                    attr = INVALID_ATTR_VISIBILITY;
                    
                case 'display' :
                    attr = INVALID_ATTR_DISPLAY;
                    
                default:
                    this.invalidateDisplay();
                    this.invalidateChildren();
                    
            }
            if (attr != INVALID_ATTR_NONE) {
                 if (this._invalidAttribute == INVALID_ATTR_NONE) {
                     topSprite.addEventListener(Event.ENTER_FRAME, invalidateAttributeHandler);
                 }
                 this._invalidAttribute |= attr;
            }
        }

        public function invalidateAttributeHandler(event:Event):Void {
            // are we in the middle of a suspendRedraw operation?
            if (null != this.svgRoot.viewer && this.svgRoot.viewer.isSuspended) {
                return;
            }
            this.topSprite.removeEventListener(Event.ENTER_FRAME, invalidateAttributeHandler);

            if (this._invalidAttribute != INVALID_ATTR_NONE) {
                if (0 != this._invalidAttribute & INVALID_ATTR_DISPLAY) {
                    if (this.getStyleOrAttr('display') == 'none') {
                        this.topSprite.visible = false;
                    }
                    else {
                        this.topSprite.visible = true;
                    }
                }
                if (0 != this._invalidAttribute & INVALID_ATTR_OPACITY) {
                    if (null != this.drawSprite)
                      this.drawSprite.alpha = SVGUnits.parseNum(this.getStyleOrAttr('opacity'));
                }
                if (0 != this._invalidAttribute & INVALID_ATTR_VISIBILITY) {
                     if (this.getStyleOrAttr('visibility') == 'hidden') {
                        this.setVisibility('hidden');
                    } else {
                        this.setVisibility('visible');
                    }
                }
                if (0 != this._invalidAttribute & INVALID_ATTR_TRANSFORM) {
                    if (Std.is( this, SVGSVGNode)) {
                        this.loadAttribute('x');
                        this.loadAttribute('y');
                        this.applyDefaultMask();
                    }
                    this.transformNode();
                    this.applyViewBox();
                }
                this._invalidAttribute = INVALID_ATTR_NONE;
            }
        }

        /*
         * Clone Handlers
         */

        public function clone():SVGNode {
            var nodeClass:Class<Dynamic> = Type.getClass(this);
            var newXML:XML = this._xml.copy();

            var node:SVGNode = cast(Type.createInstance(nodeClass, [this.svgRoot, newXML, this]), SVGNode);

            return node;
        }

        public function registerClone(clone:SVGNode):Void {
            if (this._clones.indexOf(clone) == -1) {
               this._clones.push(clone);
            }
        }

        public function unregisterClone(clone:SVGNode):Void {
            var index:Int = this._clones.indexOf(clone);
            if (index > -1) {
                this._clones = this._clones.splice(index, 1);
            }
        }

        public function updateClones():Void {
            for (i in 0...this._clones.length) {
               cast(this._clones[i], SVGNode).xml = this._xml.copy();
            }
        }

        public function getIsClone():Bool {
            return this._isClone;
        }
        
        /*
         * Animations
         */

        public function addAnimation(animation:SVGAnimateNode):Void {
            if (animations.indexOf(animation) == -1) {
                animations.push(animation);
            }
        }

        public function removeAnimation(animation:SVGAnimateNode):Void {
            if (animations.indexOf(animation) != -1) {
                animations.splice(animations.indexOf(animation), 1);
            }
        }

        /*
         * Misc Functions
         */

        public function getWidth():Float {
            var parentWidth:Float=0;
            if (Std.is( topSprite.parent, SVGViewer) ) {
                parentWidth = cast(topSprite.parent, SVGViewer).getWidth();
				//trace(parentWidth);
            }
            if (this.svgParent != null) {
                parentWidth = this.svgParent.getWidth();
				//trace(parentWidth);
            }
            if (this.getAttribute('width') != null) {
				//trace(this.getAttribute('width'));
                return SVGUnits.parseNumPct(this.getAttribute('width'), parentWidth);
            }
				//trace(parentWidth);
            // defaults to 100%
            return parentWidth;
        }

        public function getHeight():Float {
            var parentHeight:Float=0;
            if (Std.is( topSprite.parent, SVGViewer)) {
                parentHeight=cast(topSprite.parent, SVGViewer).getHeight();
            }
            if (Std.is( this.svgParent, SVGNode)) {
                parentHeight=this.svgParent.getHeight();
            }
            if (this.getAttribute('height') != null) {
                return SVGUnits.parseNumPct(this.getAttribute('height'), parentHeight);
            }

            // defaults to 100%
            return parentHeight;
        }

        /** Appends an SVGNode both to our display list as well as to our
          * XML.
          **/
        public function appendSVGChild(child:SVGNode):SVGNode {
            this._xml.appendChild(child._xml);
            this.addSVGChildAt(child, svgChildren.length);
            this.invalidateDisplay();

            return child;
        }
        
        public function removeSVGChild(node:SVGNode):SVGNode {
            // Remove from svg list.
            var i:UInt;
            for (i in 0...this.svgChildren.length) {
                if (this.svgChildren[i] == node) {
                    this.svgChildren.splice(i, 1);
                    break;
                }
            }
            
            node.svgParent = null;
            
            // unregister the element
            this.svgRoot.unregisterNode(node);
            
            // if we are dealing with a fake text node, change our
            // text node contents
            if (Std.is( node, SVGDOMTextNode) && this.hasText()) {
                this.setText(null);
            }
            
            // remove from our XML children
            for (i in 0...this._xml.children().length()) {
                if (this._xml.children()[i].attribute('__guid')== node._xml.attribute('__guid')) {
                    //delete this._xml.children()[i];
                    untyped __delete__(this._xml.children(), i);
                    break;
                }
            }
            
            // Remove from flash list.
            if ( null != node.topSprite ) {
                var child:DisplayObject;
                for (i in 0...viewBoxSprite.numChildren) {
                    child = viewBoxSprite.getChildAt(i);
                    if (child == node.topSprite) {
                        viewBoxSprite.removeChild(child);
                    }
                }
                this.invalidateDisplay();
            }

            node.onRemoved();
            
            return node;
        }
        
        /**
         * Adds newChild before refChild. Position is the position of refChild
         * to add newChild before.
         */
        public function insertSVGBefore(position:Int, newChild:SVGNode, refChild:SVGNode):Void {
            //this.dbg('insertSVGBefore, position='+position+', newChild='+newChild+', refChild='+refChild);
            // update our XML
            this._xml.insertChildBefore(refChild.xml, newChild.xml);
    
            newChild.svgParent = this;
            this.svgChildren.splice(position, 0, newChild);

            if (null != newChild.topSprite) {
                // Adjust display list position for missing
                // DOM text nodes.
                var displayPosition:Int = position;
                var i:UInt;
                for (i in 0...position) {
                    if (Std.is( this.svgChildren[i], SVGDOMTextNode)) {
                       displayPosition--;
                    }
                }
                viewBoxSprite.addChildAt(newChild.topSprite, displayPosition);
                this.invalidateDisplay();
            }
            newChild.onAdded();
        }
        
        public function addSVGChildAt(child:SVGNode, index:Int):SVGNode {
            // update XML
            if (Std.is( child, SVGDOMTextNode)) {
                if (this.hasText()) {
                    this.setText(child.xml.text().toString());
                }
            } else {
                if (index == (this._xml.children().length() - 1)) {
                    this._xml.appendChild(child.xml);
                } else {
                    var insertBefore:XML = this._xml.children()[index];
                    this._xml.insertChildBefore(insertBefore, child.xml);
                }
            }

            child.svgParent = this;
            this.svgChildren.splice(index, 0, child);

            // update our Flash display list
            if (null != child.topSprite) {
                // Adjust display list position for missing
                // DOM text nodes.
                var displayIndex:UInt = index;
                var i:UInt;
                for (i in 0...index) {
                    if (Std.is( this.svgChildren[i], SVGDOMTextNode)) {
                       displayIndex--;
                    }
                }
                viewBoxSprite.addChildAt(child.topSprite, displayIndex);
            }
            child.onAdded();
            return child;
        }
        
        /**
         * Called when a node is added to its parent.
         */
        function onAdded():Void {
            trace(topSprite.name + ' parent:' +  svgParent.topSprite.name + ' xml:' + xml.toXMLString());
        }

        /**
         * Called when a node is removed from its parent.
         */
        function onRemoved():Void {
            
        }

        /**
         * Remove all child nodes
         **/        
        function clearSVGChildren():Void {
            this.svgChildren = new Array();
            while (null != viewBoxSprite && viewBoxSprite.numChildren > 0) {
                viewBoxSprite.removeChildAt(0);
            }
        }

        
        public function getMaskAncestor():SVGNode {
            var node:SVGNode = this;
            if ( null != node && node.isMask)
                return node;
            while (null != node && !(Std.is( node, SVGSVGNode))) {
                node=node.svgParent;
                if (null != node && node.isMask)
                    return node;
            }
            return null;
        }

        public function getPatternAncestor():SVGPatternNode {
            var node:SVGNode = this;
            while (null != node && !(Std.is( node, SVGSVGNode))) {
                node=node.svgParent;
                if (Std.is( node, SVGPatternNode))
                    return cast(node, SVGPatternNode);
            }
            return null;
        }

        public function getClipPathAncestor():SVGClipPathNode {
            var node:SVGNode = this;
            while (null != node && !(Std.is( node, SVGSVGNode))) {
                node=node.svgParent;
                if (Std.is( node, SVGClipPathNode))
                    return cast(node, SVGClipPathNode);
            }
            return null;
        }
        
        public function addSVGChild(child:SVGNode):Void {
            child.svgParent = this;
            this.svgChildren.push(child);
            if (null != child.topSprite) {
                this.viewBoxSprite.addChild(child.topSprite);
                child.svgRoot.renderPending();
            }
            child.onAdded();
        }

        public function addSVGChildMask(child:SVGNode):Void {
            child.svgParent = this;
            topSprite.addChild(child.topSprite);
            child.svgRoot.renderPending();
        }
        
        /**
         * Getters / Setters
        **/
        @:setter function setXml(value:XML):XML {            
			_xml = value;
			
			if (null != this.viewBoxSprite) {
                this.clearSVGChildren();
            }

            this._parsedChildren = false;
            this.parseStyle();

            if (null == this.viewBoxSprite) {
                this.createSVGSprites();
            }

            this.invalidateDisplay();
            this.updateClones();
        	return value;
        }
        
        @:getter function getXml():XML {
            return _xml;
        }

        public function getId():String {
            var id:String = this._xml.attribute('id').toString();
            return id;
        }
        
        public function getGuid():String {
            return this._xml.attribute('__guid').toString();
        }


        public function nsQualify(name:String, nameNS:Dynamic):String {
            var n:Int;
            var scopeNS:Namespace;
            for (n in 0...this.svgRoot.xml.inScopeNamespaces().length) {
                scopeNS = this.svgRoot.xml.inScopeNamespaces()[n];
                if (scopeNS.prefix == "" && scopeNS.uri == nameNS.uri) {
                    return name;
                }
            }
            for (n in 0...this.svgRoot.xml.inScopeNamespaces().length) {
                scopeNS = this.svgRoot.xml.inScopeNamespaces()[n];
                if (scopeNS.uri == nameNS.uri) {
                    return (scopeNS.prefix + ":" + name);
                }
            }
            return name;
        }

        public function getAttrs():String {
            var attrsString:String ="";
            var attrsNamesList:XMLList = xml.attributes();
            for (i in 0...attrsNamesList.length()) {
                attrsString  += ( ' ' + attrsNamesList[i].localName() + '="' + attrsNamesList[i].toString() + '"');
                                               //this._xml.attribute('[attrsNamesList[i].name()],'"');
            }
            return attrsString;
        }

        public function getNameSpaces():String {
            var namespString:String ="";
            for (i in 0...this._xml.inScopeNamespaces().length) {
                var namesp:Namespace = this._xml.inScopeNamespaces()[i];
                if (namesp.prefix == "") {
                    namespString += [' xmlns', '=', '"',namesp.uri,'"'].join('');
                }
                else {
                    namespString += [' xmlns:', namesp.prefix, '=', '"',namesp.uri,'"'].join('');
                }
            }
            return namespString;
        }

        public function getXMLTree(indent:UInt, includeOuterTag:Bool):String {
            var i:UInt;
            var xmlString:String="";
            var myTag:String="";

            // Cloned glyphs are artificial. Skip them.
            if (Std.is( this, SVGGlyphNode) && null != this.original) {
                return "";
            }

            myTag = nsQualify(this._xml.localName(), this._xml.namespace());

            if (this.svgChildren.length > 0) {
                // Open Outer tag
                if (includeOuterTag && !(Std.is( this, SVGDOMTextNode))) {
                    for (i in 0...indent) {
                       xmlString += ' ';
                    }
                    if (Std.is( this.topSprite.parent, SVGViewer)) {
                        xmlString += ['<', myTag, this.getNameSpaces(), this.getAttrs(), '>\n'].join('');
                    }
                    else {
                        xmlString += ['<', myTag, this.getAttrs(), '>\n'].join('');
                    }
                }
                // Children
                for (i in 0...this.svgChildren.length) {
                    xmlString += this.svgChildren[i].getXMLTree(indent+(includeOuterTag ? 2 :0), true);
                }
                // Close Outer Tag
                if (includeOuterTag && !(Std.is( this, SVGDOMTextNode))) {
                    for (i in 0...indent) {
                       xmlString += ' ';
                    }
                    xmlString += ['</', myTag, '>\n'].join('');
                }
            }
            else {
                if (Std.is( this, SVGDOMTextNode)) {
                    if (null != cast(this, SVGDOMTextNode).nodeValue) {
                        xmlString += [cast(this, SVGDOMTextNode).nodeValue].join('');
                    }
                }
                else if (Std.is( this, SVGScriptNode)) {
                    var content:String;
                    if (this._xml.children().length() > 0) {
                        content = this._xml.children()[0].text().toString();
                        xmlString += ['<', myTag, this.getAttrs(), '><![CDATA[\n', content, ']]></', myTag, '>\n'].join('');
                    }
                    else {
                        xmlString = ['<', myTag, this.getAttrs(), '/>\n'].join('');
                    }
                }
                else {
                    if (includeOuterTag) {
                        for (i in 0...indent) {
                           xmlString += ' ';
                        }
                        xmlString += '<' + myTag + this.getAttrs() + '/>\n';
                    }
                }
            }
            return xmlString;
        }


        public function getInvalidDisplay():Bool {
            return this._invalidDisplay;
        }


        public function dbg(debugString:String, ?info:PosInfos):Void {
            this.svgRoot.debug(debugString, info);
        }
        
        public function err(errorString:String):Void {
            this.svgRoot.error(errorString);
        }
        
        /** Functions for profiling. */
        public function start(subject:String, ?subjectStarted:String = null):Void {
            this.svgRoot.start(subject, subjectStarted);
        }
        
        /** Functions for profiling. */
        public function end(subject:String, ?subjectStarted:String = null):Void {
            this.svgRoot.end(subject, subjectStarted);
        }
        
        /** Functions for profiling. */
        public function increment(subject:String, amount:Int):Void {
            this.svgRoot.increment(subject, amount);
        }

		
    }
